/*
 * Copyright (c) 2002, 2003 Marcus Overhagen <Marcus@Overhagen.de>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files or portions
 * thereof (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright notice
 *    in the  binary, as well as this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided with
 *    the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

/* Implements _event_queue_imp used by BTimedEventQueue, not thread save!
 */
#include <string.h>

#include <Autolock.h>
#include <Buffer.h>
#include <InterfaceDefs.h> //defines B_DELETE
#include <TimedEventQueue.h>
#include <TimeSource.h>

#include "TimedEventQueuePrivate.h"

#include "Debug.h"
#include "MediaDebug.h"

_event_queue_imp::_event_queue_imp() :
	fLock(new BLocker("BTimedEventQueue locker")),
	fEventCount(0),
	fFirstEntry(NULL),
	fLastEntry(NULL),
	fCleanupHookContext(NULL),
	fCleanupHook(0)
{
}


_event_queue_imp::~_event_queue_imp()
{
	event_queue_entry *entry;
	entry = fFirstEntry;
	while (entry) {
		event_queue_entry *deleteme;
		deleteme = entry;
		entry = entry->next;
		delete deleteme;
	}
	delete fLock;
}


status_t
_event_queue_imp::AddEvent(const media_timed_event &event)
{
	BAutolock lock(fLock);

//	printf("        adding      %12Ld  at %12Ld\n", event.event_time, system_time());

	if (event.type <= 0) {
		 return B_BAD_VALUE;
	}

	//create a new queue
	if (fFirstEntry == NULL) {
		ASSERT(fEventCount == 0);
		ASSERT(fLastEntry == NULL);
		fFirstEntry = fLastEntry = new event_queue_entry;
		fFirstEntry->event = event;
		fFirstEntry->prev = NULL;
		fFirstEntry->next = NULL;
		fEventCount++;
		return B_OK;
	}

	//insert at queue begin
	if (fFirstEntry->event.event_time >= event.event_time) {
		event_queue_entry *newentry = new event_queue_entry;
		newentry->event = event;
		newentry->prev = NULL;
		newentry->next = fFirstEntry;
		fFirstEntry->prev = newentry;
		fFirstEntry = newentry;
		fEventCount++;
		return B_OK;
	}

	//insert at queue end
	if (fLastEntry->event.event_time <= event.event_time) {
		event_queue_entry *newentry = new event_queue_entry;
		newentry->event = event;
		newentry->prev = fLastEntry;
		newentry->next = NULL;
		fLastEntry->next = newentry;
		fLastEntry = newentry;
		fEventCount++;
		return B_OK;
	}

	//insert into the queue
	for (event_queue_entry *entry = fLastEntry; entry; entry = entry->prev) {
		if (entry->event.event_time <= event.event_time) {
			//insert after entry
			event_queue_entry *newentry = new event_queue_entry;
			newentry->event = event;
			newentry->prev = entry;
			newentry->next = entry->next;
			(entry->next)->prev = newentry;
			entry->next = newentry;
			fEventCount++;
			return B_OK;
		}
	}

	debugger("_event_queue_imp::AddEvent failed, should not be here\n");
	return B_OK;
}


status_t
_event_queue_imp::RemoveEvent(const media_timed_event *event)
{
	BAutolock lock(fLock);

	for (event_queue_entry *entry = fFirstEntry; entry; entry = entry->next) {
		if (entry->event == *event) {
			// No cleanup here
			RemoveEntry(entry);
			return B_OK;
		}
	}

	return B_ERROR;
}


status_t
_event_queue_imp::RemoveFirstEvent(media_timed_event * outEvent)
{
	BAutolock lock(fLock);

	if (fFirstEntry == 0)
		return B_ERROR;

	if (outEvent != 0) {
		// No cleanup here
		*outEvent = fFirstEntry->event;
	} else {
		CleanupEvent(&fFirstEntry->event);
	}

	RemoveEntry(fFirstEntry);

	return B_OK;
}


bool
_event_queue_imp::HasEvents() const
{
	return fEventCount != 0;
}


int32
_event_queue_imp::EventCount() const
{
	#if DEBUG > 1
		Dump();
	#endif
	return fEventCount;
}


const media_timed_event *
_event_queue_imp::FirstEvent() const
{
	return fFirstEntry ? &fFirstEntry->event : NULL;
}


bigtime_t
_event_queue_imp::FirstEventTime() const
{
	return fFirstEntry ? fFirstEntry->event.event_time : B_INFINITE_TIMEOUT;
}


const media_timed_event *
_event_queue_imp::LastEvent() const
{
	return fLastEntry ? &fLastEntry->event : NULL;
}


bigtime_t
_event_queue_imp::LastEventTime() const
{
	return fLastEntry ? fLastEntry->event.event_time : B_INFINITE_TIMEOUT;
}


const media_timed_event *
_event_queue_imp::FindFirstMatch(bigtime_t eventTime,
								 BTimedEventQueue::time_direction direction,
								 bool inclusive,
								 int32 eventType)
{
	event_queue_entry *end;
	event_queue_entry *entry;

	BAutolock lock(fLock);

	switch (direction) {
		case BTimedEventQueue::B_ALWAYS:
			for (entry = fFirstEntry; entry; entry = entry->next) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type)
					return &entry->event;
			}
			return NULL;

		case BTimedEventQueue::B_BEFORE_TIME:
			end = GetEnd_BeforeTime(eventTime, inclusive);
			if (end == NULL)
				return NULL;
			end = end->next;
			for (entry = fFirstEntry; entry != end; entry = entry->next) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
					return &entry->event;
				}
			}
			return NULL;

		case BTimedEventQueue::B_AT_TIME:
			{
				bool found_time = false;
				for (entry = fFirstEntry; entry; entry = entry->next) {
					if (eventTime == entry->event.event_time) {
						found_time = true;
						if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type)
							return &entry->event;
					} else if (found_time)
						return NULL;
				}
				return NULL;
			}

		case BTimedEventQueue::B_AFTER_TIME:
			for (entry = GetStart_AfterTime(eventTime, inclusive); entry; entry = entry->next) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
					return &entry->event;
				}
			}
			return NULL;
	}

	return NULL;
}


status_t
_event_queue_imp::DoForEach(BTimedEventQueue::for_each_hook hook,
							void *context,
							bigtime_t eventTime,
							BTimedEventQueue::time_direction direction,
							bool inclusive,
							int32 eventType)
{
	event_queue_entry *end;
	event_queue_entry *entry;
	event_queue_entry *remove;
	BTimedEventQueue::queue_action action;

	BAutolock lock(fLock);

	switch (direction) {
		case BTimedEventQueue::B_ALWAYS:
			for (entry = fFirstEntry; entry; ) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
					action = (*hook)(&entry->event, context);
					switch (action) {
						case BTimedEventQueue::B_DONE:
							return B_OK;
						case BTimedEventQueue::B_REMOVE_EVENT:
							CleanupEvent(&entry->event);
							remove = entry;
							entry = entry->next;
							RemoveEntry(remove);
							break;
						case BTimedEventQueue::B_NO_ACTION:
						case BTimedEventQueue::B_RESORT_QUEUE:
						default:
							entry = entry->next;
							break;
					}
				} else {
					entry = entry->next;
				}
			}
			return B_OK;

		case BTimedEventQueue::B_BEFORE_TIME:
			end = GetEnd_BeforeTime(eventTime, inclusive);
			if (end == NULL)
				return B_OK;
			end = end->next;
			for (entry = fFirstEntry; entry != end; ) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
					action = (*hook)(&entry->event, context);
					switch (action) {
						case BTimedEventQueue::B_DONE:
							return B_OK;
						case BTimedEventQueue::B_REMOVE_EVENT:
							CleanupEvent(&entry->event);
							remove = entry;
							entry = entry->next;
							RemoveEntry(remove);
							break;
						case BTimedEventQueue::B_NO_ACTION:
						case BTimedEventQueue::B_RESORT_QUEUE:
						default:
							entry = entry->next;
							break;
					}
				} else {
					entry = entry->next;
				}
			}
			return B_OK;

		case BTimedEventQueue::B_AT_TIME:
			{
				bool found_time = false;
				for (entry = fFirstEntry; entry; ) {
					if (eventTime == entry->event.event_time) {
						found_time = true;
						if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
							action = (*hook)(&entry->event, context);
							switch (action) {
								case BTimedEventQueue::B_DONE:
									return B_OK;
								case BTimedEventQueue::B_REMOVE_EVENT:
									CleanupEvent(&entry->event);
									remove = entry;
									entry = entry->next;
									RemoveEntry(remove);
									break;
								case BTimedEventQueue::B_NO_ACTION:
								case BTimedEventQueue::B_RESORT_QUEUE:
								default:
									entry = entry->next;
									break;
							}
						} else {
							entry = entry->next;
						}
					} else if (found_time) {
						break;
					} else {
						entry = entry->next;
					}
				}
				return B_OK;
			}

		case BTimedEventQueue::B_AFTER_TIME:
			for (entry = GetStart_AfterTime(eventTime, inclusive); entry; ) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
					action = (*hook)(&entry->event, context);
					switch (action) {
						case BTimedEventQueue::B_DONE:
							return B_OK;
						case BTimedEventQueue::B_REMOVE_EVENT:
							CleanupEvent(&entry->event);
							remove = entry;
							entry = entry->next;
							RemoveEntry(remove);
							break;
						case BTimedEventQueue::B_NO_ACTION:
						case BTimedEventQueue::B_RESORT_QUEUE:
						default:
							entry = entry->next;
							break;
					}
				} else {
					entry = entry->next;
				}
			}
			return B_OK;
	}

	return B_ERROR;
}


status_t
_event_queue_imp::FlushEvents(bigtime_t eventTime,
							  BTimedEventQueue::time_direction direction,
							  bool inclusive,
							  int32 eventType)
{
	event_queue_entry *end;
	event_queue_entry *entry;
	event_queue_entry *remove;

	BAutolock lock(fLock);

	switch (direction) {
		case BTimedEventQueue::B_ALWAYS:
			for (entry = fFirstEntry; entry; ) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
					CleanupEvent(&entry->event);
					remove = entry;
					entry = entry->next;
					RemoveEntry(remove);
				} else {
					entry = entry->next;
				}
			}
			return B_OK;

		case BTimedEventQueue::B_BEFORE_TIME:
			end = GetEnd_BeforeTime(eventTime, inclusive);
			if (end == NULL)
				return B_OK;
			end = end->next;
			for (entry = fFirstEntry; entry != end; ) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
					CleanupEvent(&entry->event);
					remove = entry;
					entry = entry->next;
					RemoveEntry(remove);
				} else {
					entry = entry->next;
				}
			}
			return B_OK;

		case BTimedEventQueue::B_AT_TIME:
			{
				bool found_time = false;
				for (entry = fFirstEntry; entry; ) {
					if (eventTime == entry->event.event_time) {
						found_time = true;
						if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
							CleanupEvent(&entry->event);
							remove = entry;
							entry = entry->next;
							RemoveEntry(remove);
						} else {
							entry = entry->next;
						}
					} else if (found_time) {
						break;
					} else {
						entry = entry->next;
					}
				}
				return B_OK;
			}

		case BTimedEventQueue::B_AFTER_TIME:
			for (entry = GetStart_AfterTime(eventTime, inclusive); entry; ) {
				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
					CleanupEvent(&entry->event);
					remove = entry;
					entry = entry->next;
					RemoveEntry(remove);
				} else {
					entry = entry->next;
				}
			}
			return B_OK;
	}

	return B_ERROR;
}


void
_event_queue_imp::SetCleanupHook(BTimedEventQueue::cleanup_hook hook, void *context)
{
	BAutolock lock(fLock);
	fCleanupHookContext = context;
	fCleanupHook = hook;
}


void
_event_queue_imp::RemoveEntry(event_queue_entry *entry)
{
	//remove the entry from double-linked list
	//and delete it
	if (entry->prev != NULL)
		(entry->prev)->next = entry->next;
	if (entry->next != NULL)
		(entry->next)->prev = entry->prev;
	if (entry == fFirstEntry) {
		fFirstEntry = entry->next;
		if (fFirstEntry != NULL)
			fFirstEntry->prev = NULL;
	}
	if (entry == fLastEntry) {
		fLastEntry = entry->prev;
		if (fLastEntry != NULL)
			fLastEntry->next = NULL;
	}
	delete entry;
	fEventCount--;
	ASSERT(fEventCount >= 0);
	ASSERT(fEventCount != 0 || ((fFirstEntry == NULL) && (fLastEntry == NULL)));
}


void
_event_queue_imp::CleanupEvent(media_timed_event *event)
{
	//perform the cleanup action required
	//when deleting an event from the queue

	//BeBook says:
	//	Each event has a cleanup flag associated with it that indicates
	//  what sort of special action needs to be performed when the event is
	//  removed from the queue. If this value is B_NO_CLEANUP, nothing is done.
	//  If it's B_RECYCLE, and the event is a B_HANDLE_BUFFER event, BTimedEventQueue
	//  will automatically recycle the buffer associated with the event.
	//  If the cleanup flag is B_DELETE or is B_USER_CLEANUP or greater,
	//  the cleanup hook function will be called.
	//and:
	//  cleanup hook function specified by hook to be called for events
	//  as they're removed from the queue. The hook will be called only
	//  for events with cleanup_flag values of B_DELETE or B_USER_CLEANUP or greater.
	//and:
	//  These values define how BTimedEventQueue should handle removing
	//  events from the queue. If the flag is B_USER_CLEANUP or greater,
	//  the cleanup hook function is called when the event is removed.
	//Problems:
	//  B_DELETE is a keyboard code! (seems to have existed in early
	//  sample code as a cleanup flag)
	//
	//  exiting cleanup flags are:
	//		B_NO_CLEANUP = 0,
	//		B_RECYCLE_BUFFER,		// recycle buffers handled by BTimedEventQueue
	//		B_EXPIRE_TIMER,			// call TimerExpired() on the event->data
	//		B_USER_CLEANUP = 0x4000	// others go to the cleanup func
	//

	if (event->cleanup == BTimedEventQueue::B_NO_CLEANUP) {
		// do nothing
	} else if (event->type == BTimedEventQueue::B_HANDLE_BUFFER
			&& event->cleanup == BTimedEventQueue::B_RECYCLE_BUFFER) {
		((BBuffer *)event->pointer)->Recycle();
		DEBUG_ONLY(*const_cast<void **>(&event->pointer) = NULL);
	} else if (event->cleanup == BTimedEventQueue::B_EXPIRE_TIMER) {
		// do nothing, called in BMediaEventLooper::DispatchEvent
	} else if (event->cleanup == B_DELETE
			|| event->cleanup >= BTimedEventQueue::B_USER_CLEANUP) {
		if (fCleanupHook)
			(*fCleanupHook)(event,fCleanupHookContext);
	} else {
		ERROR("BTimedEventQueue cleanup unhandled! type = %" B_PRId32
			", cleanup = %" B_PRId32 "\n", event->type, event->cleanup);
	}
}


_event_queue_imp::event_queue_entry *
_event_queue_imp::GetEnd_BeforeTime(bigtime_t eventTime, bool inclusive)
{
	event_queue_entry *entry;

	entry = fLastEntry;
	while (entry) {
		if ((entry->event.event_time > eventTime) || (!inclusive && entry->event.event_time == eventTime))
			entry = entry->prev;
		else
			break;
	}
	return entry;
}


_event_queue_imp::event_queue_entry *
_event_queue_imp::GetStart_AfterTime(bigtime_t eventTime, bool inclusive)
{
	event_queue_entry *entry;

	entry = fFirstEntry;
	while (entry) {
		if ((entry->event.event_time < eventTime) || (!inclusive && entry->event.event_time == eventTime))
			entry = entry->next;
		else
			break;
	}
	return entry;
}


#if DEBUG > 1
void
_event_queue_imp::Dump() const
{
	TRACE("fEventCount = %" B_PRId32 "\n", fEventCount);
	TRACE("fFirstEntry = 0x%p\n", (void*)fFirstEntry);
	TRACE("fLastEntry  = 0x%p\n", (void*)fLastEntry);
	for (event_queue_entry *entry = fFirstEntry; entry; entry = entry->next) {
		TRACE("entry = 0x%p\n", (void*)entry);
		TRACE("  entry.prev = 0x%p\n", (void*)entry->prev);
		TRACE("  entry.next = 0x%p\n", (void*)entry->next);
		TRACE("  entry.event.event_time = %" B_PRId64 "\n",
			entry->event.event_time);
	}
}
#endif
